slick-codegenを使ってテーブルコードを自動生成する。そしてPostgresqlへCRUDする。
はじめに
「プログラムからどのようにしてデータベースへアクセスするか」は常に検討課題にあがります。いまやOracleやMySQLにとどまらず、本当に多くの選択肢がデータ永続化の候補として存在しますが、「RDBをデータベースとして選択し、O/Rマッパーを使ってプログラムから制御する」という考え方は今後も生き続けることでしょう。
今回はそんな状況で少しでも楽できることを示すのが目的です。例として、データベースはPostgresql、プログラムはScalaを選択したときに、slick-codegenを使うことで、CRUDを行うためのお膳立てを自動化してみます。
使うもの
以下の構成で行きます。Herokuの無料Postgresqlを使います。
項目 | 内容 |
---|---|
フレームワーク | Playframework |
言語 | Scala |
O/R mapper | Slick 3 |
Database | Postgresql (on Heroku) |
やること
以下の順で進めていきます。
- Postgresqlのテーブルを作成する
- slick-codegenを使ってテーブルコードを自動生成する
- 生成したコードを使ってCRUD操作してみる
Postgresqlのテーブルを作成する
まずはHeroku上でPostgresqlを使えるようにします。
次に、接続情報を用いて、手元のIntellijからPostgresqlへ接続します。このとき、以下SSLに関するプロパティの設定が必要であることに注意してください。
ssl true
sslmode require
sslfactory org.postgresql.ssl.NonValidatingFactory
データベースへ接続できたら、テーブルを作成します。Intellijの力を借りつつ以下のようにUser
テーブルとTask
テーブルを作成しました。
CREATE TABLE "user" ( id INTEGER PRIMARY KEY NOT NULL, username VARCHAR(50) NOT NULL, age INTEGER, address TEXT ); CREATE UNIQUE INDEX user_id_uindex ON "user" (id)
CREATE TABLE task ( id INTEGER PRIMARY KEY NOT NULL, user_id INTEGER NOT NULL, taskname VARCHAR(500) NOT NULL, enddate DATE, CONSTRAINT task_user_id_fk FOREIGN KEY (user_id) REFERENCES "user" (id) ); CREATE UNIQUE INDEX task_id_uindex ON task (id)
これで、ソースコード生成対象のテーブル作成がおわりました。複数テーブル作成しましたが、今回は例示ということでUser
テーブルのソースコードを生成します。
slick-codegenを使ってテーブルコードを自動生成する
User
テーブルを自動生成します。まず、sbtの設定ファイルを以下のようにしてください。
libraryDependencies ++= Seq( jdbc, cache, ws, "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test, "com.typesafe.slick" % "slick-codegen_2.11" % "3.1.1", // -- ① "org.postgresql" % "postgresql" % "9.4.1208", // -- ② "com.typesafe.play" %% "play-slick" % "2.0.0" )
- ① slick-codegen: テーブルコード生成のためのライブラリです。
- ② postgresql: テーブルコードを生成するためには生成対象のテーブルに合ったドライバが必要になります。今回は Postgresqlですのでこれを指定します。
自動生成するコードを書きます。非常に簡単で、Postgresqlへ設定するための情報を定義したら、それをSourceCodeGenerator
に渡すだけで済みます。
package infrastructures.slick.generator import slick.codegen.SourceCodeGenerator object SlickModelGenerator { def main(args: Array[String]): Unit = { val slickDriver = "slick.driver.PostgresDriver" // -- ① val jdbcDriver = "org.postgresql.Driver" // -- ② val url = // -- ③ """jdbc:postgresql://xxxxx:5432 |/task_management |?ssl=true |&sslfactory=org.postgresql.ssl.NonValidatingFactory""".stripMargin val user = "y.wada" val password = "f8wjfowebnskf" val outputFolder = "app" // -- ④ val pkg = "infrastructures.models" // -- ⑤ SourceCodeGenerator.main( Array( slickDriver, jdbcDriver, url, outputFolder, pkg, user, password ) ) } }
- ①: Slickのドライバを指定します。
- ②: JDBCのドライバを指定します。
- ③: 接続先URLを指定します。スキーマ名まで含める必要があること、先でIntellijから接続する際に設定したSSL関係の情報をパラメータとして記述する必要があることに注意してくdささい。
- ④: 出力先ディレクトリを指定します。パッケージ名ではないことに注意してください。
- ⑤: パッケージを指定します。④と合わせると、app/infrastructures/models に出力されます。そして、
package infrastructures.models
が付与された状態となります。
準備完了です。実行します。
これで、infrastructures.models
にTables.scala
が生成されるはずです。
生成したコードを使ってCRUD操作してみる
Tables
が生成されれば、それを使ってCRUD操作ができるようになります。Controllerに記述して動作を確認してみましょう。
slick.dbs.default { driver = "slick.driver.PostgresDriver$" db.driver = "org.postgresql.Driver" db.url = "jdbc:postgresql:/xxxxx:5432/xxxxx?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory" db.user = "username" db.password = "password" db.connectionTimeout = 10000 }
package controllers.user import javax.inject.Inject import core.util.FutureSupport import infrastructures.models.Tables import infrastructures.models.Tables.UserRow import play.api.db.slick.DatabaseConfigProvider import play.api.libs.json.{ JsPath, Reads } import play.api.mvc.{ Action, Controller } import slick.driver.JdbcProfile import scala.concurrent.ExecutionContext class UserController @Inject()( dbConfigProvider: DatabaseConfigProvider )(implicit ec: ExecutionContext) extends Controller { import slick.driver.PostgresDriver.api._ import RequestBodyConverters._ val dbConfig = dbConfigProvider.get[JdbcProfile] /*ここにCRUD相当のメソッドを記述していきます*/ } object RequestBodyConverters { import play.api.libs.functional.syntax._ implicit val userReads: Reads[User] = ( (JsPath \ "username").read[String] and (JsPath \ "address").readNullable[String] and (JsPath \ "age").readNullable[Int] ) (User) } case class User( userName: String, address: Option[String], age: Option[Int] )
Create
def get(userId: Int) = Action.async { req => val result = dbConfig.db.run(Tables.User.filter(_.id === userId).result) result.map(user => Ok(user.head.username)) }
Read
def insert = Action.async(parse.json) { req => for { addUser <- FutureSupport.jsResultToFuture(req.body.validate[User]) insertAction = Tables.User += UserRow(0, addUser.userName, addUser.age, addUser.address) result <- dbConfig.db.run(insertAction) } yield Created }
Update
def update(userId: Int) = Action.async(parse.json) { req => for { user <- FutureSupport.jsResultToFuture(req.body.validate[User]) insertUpdate = Tables.User.update(Tables.UserRow( userId, user.userName, user.age, user.address )) result <- dbConfig.db.run(insertUpdate) } yield NoContent }
Delete
def delete(userId: Int) = Action.async { req => val result = dbConfig.db.run(Tables.User.filter(_.id === userId).delete) result.map(_ => NoContent) }
試す
POSTメソッドでinsertします。ChromeエクステンションのDHCというツールを使ってPOSTしています。
データが作成されていることが確認できました。Auto incrementもちゃんと機能しています。
おわりに
slick-codegen
を使って、テーブルコードを自動生成し、それを使ってCRUD操作を行うことができました。ORマッパーは便利な反面、利用を開始するまでの手続きやお作法が多く、面食らうことも多いため、今回のようにツールにまかせて自動化できるところはどんどん自動化してやりましょう。我々が本当に書きたいコードはデータベースに接続するコードではなく、データベースに保存されたデータを用いてビジネスロジックを組み立てる方です。皆様のコーディングの効率化に少しでもお役に立てれば幸いです。